{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Regression Trees"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np \n",
    "import matplotlib.pyplot as plt\n",
    "import seaborn as sns\n",
    "from sklearn import datasets"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [],
   "source": [
    "tips = sns.load_dataset('tips')\n",
    "X = np.array(tips.drop(columns = 'tip'))\n",
    "y = np.array(tips['tip'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {},
   "outputs": [],
   "source": [
    "def RSS(a, b):\n",
    "    return sum((a - np.mean(a))**2) + np.sum((b - np.mean(b))**2)\n",
    "\n",
    "def sort_x_by_y(x, y):\n",
    "    unique_xs = np.unique(x)\n",
    "    y_mean_by_x = np.array([y[x == unique_x].mean() for unique_x in unique_xs])\n",
    "    ordered_xs = unique_xs[np.argsort(y_mean_by_x)]\n",
    "    return ordered_xs\n",
    "\n",
    "def all_rows_equal(X):\n",
    "    return (X == X[0]).all()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Node:\n",
    "    \n",
    "    def __init__(self, Xsub, ysub, ID, depth = 0, parent_ID = None, leaf = True):\n",
    "        self.ID = ID\n",
    "        self.Xsub = Xsub\n",
    "        self.ysub = ysub\n",
    "        self.size = len(ysub)\n",
    "        self.depth = depth\n",
    "        self.parent_ID = parent_ID\n",
    "        self.leaf = leaf\n",
    "        \n",
    "\n",
    "class Splitter:\n",
    "    \n",
    "    def __init__(self):\n",
    "        self.rss = np.inf\n",
    "        \n",
    "    def replace_split(self, rss, parent_ID, d, dtype = 'quant', t = None, L_values = None):\n",
    "        self.rss = rss\n",
    "        self.parent_ID = parent_ID\n",
    "        self.d = d\n",
    "        self.dtype = dtype\n",
    "        self.t = t        \n",
    "        self.L_values = L_values        "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {},
   "outputs": [],
   "source": [
    "class DecisionTreeRegressor:\n",
    "    \n",
    "    #############################\n",
    "    ######## 1. TRAINING ########\n",
    "    #############################\n",
    "    \n",
    "    ######### FIT ##########\n",
    "    def fit(self, X, y, max_depth = 100, min_size = 2):\n",
    "        \n",
    "        ## Add data\n",
    "        self.X = X\n",
    "        self.y = y\n",
    "        self.N, self.D = self.X.shape\n",
    "        dtypes = [np.array(list(self.X[:,d])).dtype for d in range(self.D)]\n",
    "        self.dtypes = ['quant' if (dtype == float or dtype == int) else 'cat' for dtype in dtypes]\n",
    "        \n",
    "        ## Add regularization parameters\n",
    "        self.max_depth = max_depth\n",
    "        self.min_size = min_size\n",
    "        \n",
    "        ## Initialize nodes\n",
    "        self.nodes_dict = {}\n",
    "        self.current_ID = 0\n",
    "        initial_node = Node(Xsub = X, ysub = y, ID = self.current_ID, parent_ID = None)\n",
    "        self.nodes_dict[self.current_ID] = initial_node\n",
    "        self.current_ID += 1\n",
    "        \n",
    "        ## Build\n",
    "        self.build()\n",
    "        \n",
    "        ## Calculate leaf means\n",
    "        self.get_leaf_means()\n",
    "     \n",
    "    \n",
    "    ###### FIND SPLIT ######\n",
    "    def find_split(self, eligible_parents):\n",
    "        \n",
    "        ## Instantiate splitter\n",
    "        splitter = Splitter()\n",
    "        \n",
    "        ## For each eligible parent node...\n",
    "        for parent_ID, parent in eligible_parents.items():\n",
    "            ysub = parent.ysub\n",
    "            \n",
    "            ## For each predictor...\n",
    "            for d in range(self.D):\n",
    "                Xsub_d = parent.Xsub[:,d]\n",
    "                dtype = self.dtypes[d]\n",
    "                if len(np.unique(Xsub_d)) == 1:\n",
    "                    continue\n",
    "                    \n",
    "                ## For each threshold value...\n",
    "                if dtype == 'quant':\n",
    "                    for t in np.unique(Xsub_d)[:-1]:\n",
    "                        ysub_L = ysub[Xsub_d <= t]\n",
    "                        ysub_R = ysub[Xsub_d > t]\n",
    "                        rss = RSS(ysub_L, ysub_R)\n",
    "                        if rss < splitter.rss:\n",
    "                            splitter.replace_split(rss, parent_ID, d, dtype = 'quant', t = t)\n",
    "                else:\n",
    "                    ordered_x = sort_x_by_y(Xsub_d, ysub)\n",
    "                    for i in range(len(ordered_x) - 1):\n",
    "                        L_values = ordered_x[:i+1]\n",
    "                        ysub_L = ysub[np.isin(Xsub_d, L_values)]\n",
    "                        ysub_R = ysub[~np.isin(Xsub_d, L_values)]\n",
    "                        rss = RSS(ysub_L, ysub_R)\n",
    "                        if rss < splitter.rss: \n",
    "                            splitter.replace_split(rss, parent_ID, d, dtype = 'cat', L_values = L_values)\n",
    "        ## Save splitter\n",
    "        self.splitter = splitter\n",
    "    \n",
    "    ###### MAKE SPLIT ######\n",
    "    def make_split(self):\n",
    "        ## Update parent nodes\n",
    "        parent_node = self.nodes_dict[self.splitter.parent_ID]\n",
    "        parent_node.leaf = False\n",
    "        parent_node.child_L = self.current_ID\n",
    "        parent_node.child_R = self.current_ID + 1\n",
    "        parent_node.d = self.splitter.d\n",
    "        parent_node.dtype = self.splitter.dtype\n",
    "        parent_node.t = self.splitter.t        \n",
    "        parent_node.L_values = self.splitter.L_values\n",
    "        \n",
    "        ## Get X and y data for children\n",
    "        if parent_node.dtype == 'quant':\n",
    "            L_condition = parent_node.Xsub[:,parent_node.d] <= parent_node.t\n",
    "     \n",
    "        else:\n",
    "            L_condition = np.isin(parent_node.Xsub[:,parent_node.d], parent_node.L_values)\n",
    "        Xchild_L = parent_node.Xsub[L_condition]\n",
    "        ychild_L = parent_node.ysub[L_condition]\n",
    "        Xchild_R = parent_node.Xsub[~L_condition]\n",
    "        ychild_R = parent_node.ysub[~L_condition]\n",
    "\n",
    "        \n",
    "        ## Create child nodes\n",
    "        child_node_L = Node(Xchild_L, ychild_L, depth = parent_node.depth + 1,\n",
    "                            ID = self.current_ID, parent_ID = parent_node.ID)\n",
    "        child_node_R = Node(Xchild_R, ychild_R, depth = parent_node.depth + 1,\n",
    "                            ID = self.current_ID+1, parent_ID = parent_node.ID)\n",
    "        self.nodes_dict[self.current_ID] = child_node_L\n",
    "        self.nodes_dict[self.current_ID + 1] = child_node_R\n",
    "        self.current_ID += 2\n",
    "    \n",
    "    ###### BUILD TREE ######\n",
    "    def build(self):\n",
    "        \n",
    "        eligible_parents = self.nodes_dict\n",
    "        while True:\n",
    "                        \n",
    "            ## Find split among eligible parent nodes\n",
    "            self.find_split(eligible_parents)\n",
    "            \n",
    "            ## Make split\n",
    "            self.make_split()\n",
    "            \n",
    "            ## Find eligible nodes for next iteration\n",
    "            eligible_parents = {ID:node for (ID, node) in self.nodes_dict.items() if \n",
    "                                (node.leaf == True) &\n",
    "                                (node.depth < self.max_depth) &\n",
    "                                (node.size >= self.min_size) & \n",
    "                                (~all_rows_equal(node.Xsub))}\n",
    "            \n",
    "            ## Quit if no more eligible parents\n",
    "            if len(eligible_parents) == 0:\n",
    "                break\n",
    "                \n",
    "                \n",
    "    ###### LEAF MEANS ######\n",
    "    def get_leaf_means(self):\n",
    "        self.leaf_means = {}\n",
    "        for node_ID, node in self.nodes_dict.items():\n",
    "            if node.leaf:\n",
    "                self.leaf_means[node_ID] = node.ysub.mean()\n",
    "\n",
    "            \n",
    "    #############################\n",
    "    ####### 2. PREDICTING #######\n",
    "    #############################\n",
    "    \n",
    "    ####### PREDICT ########\n",
    "    def predict(self, X_test):\n",
    "        yhat = []\n",
    "        for x in X_test:\n",
    "            node = self.nodes_dict[0] \n",
    "            while not node.leaf:\n",
    "                if node.dtype == 'quant':\n",
    "                    if x[node.d] <= node.t:\n",
    "                        node = self.nodes_dict[node.child_L]\n",
    "                    else:\n",
    "                        node = self.nodes_dict[node.child_R]\n",
    "                else:\n",
    "                    if x[node.d] in node.L_values:\n",
    "                        node = self.nodes_dict[node.child_L]\n",
    "                    else:\n",
    "                        node = self.nodes_dict[node.child_R]\n",
    "            yhat.append(self.leaf_means[node.ID])\n",
    "        return np.array(yhat)\n",
    "            \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {},
   "outputs": [],
   "source": [
    "np.random.seed(123)\n",
    "test_frac = 0.25\n",
    "test_size = int(len(y)*test_frac)\n",
    "test_idxs = np.random.choice(np.arange(len(y)), test_size, replace = False)\n",
    "X_train = X[~test_idxs]\n",
    "y_train = y[~test_idxs]\n",
    "X_test = X[test_idxs]\n",
    "y_test = y[test_idxs]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "metadata": {},
   "outputs": [],
   "source": [
    "tree = DecisionTreeRegressor()\n",
    "tree.fit(X_train, y_train, max_depth = 5, min_size = 10)\n",
    "y_test_hat = tree.predict(X_test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "sns.scatterplot(y_train, tree.predict(X_train));"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
